package com.yin.myproject.zk.recipes.lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author Eason
 * @create 2018-04-12 14:48
 **/
public class ExclusiveLock01 implements Lock, Watcher {

    private static final String ROOT_LOCK = "/locks";
    private String lockName;
    private String lockPath;
    private ZooKeeper zooKeeper;
    private CountDownLatch connectLatch = new CountDownLatch(1);
    private CyclicBarrier lockBarrier = new CyclicBarrier(2);
    private String id = UUID.randomUUID().toString();
    private LockStatus lockStatus = LockStatus.UNLOCK;


    public ExclusiveLock01(String lockName) throws IOException, InterruptedException {
        this.lockName = lockName;
        this.lockPath = ROOT_LOCK + "/" + lockName;
        zooKeeper = new ZooKeeper("192.168.212.104:2181", 60 * 60 * 1000, this);
        connectLatch.await();
        System.out.println("[" + id + "]建立zk连接成功");
    }

    @Override
    public void lock() {
        if (lockStatus == LockStatus.LOCKED) {
            return;
        }
        try {
            //1.判断节点存不存在
            Stat stat = zooKeeper.exists(ROOT_LOCK + "/" + lockName, this);
            //2.如果不存在 创建节点 表示获取锁
            if (stat == null) {
                zooKeeper.create(this.lockPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                this.lockStatus = LockStatus.LOCKED;
                System.out.println("[" + id + "]获得锁");
                return;
            }
            //3.如果存在,设置锁的状态为TRY_LOCK 并使用栅栏让其等待
            //栅栏什么时候打开?对节点的删除操作设置监控
            lockStatus = LockStatus.TRY_LOCK;
            lockBarrier.await();
        } catch (KeeperException | InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        //状态判断
        if (lockStatus == LockStatus.UNLOCK) {
            return;
        }
        try {
            Stat stat = zooKeeper.exists(lockPath, false);
            zooKeeper.delete(lockPath, stat.getVersion());
            lockStatus = LockStatus.UNLOCK;
            lockBarrier.reset();
            System.out.println("[" + id + "]释放锁");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void process(WatchedEvent event) {
        //1. 监控连接
        if (event.getState() == Event.KeeperState.SyncConnected && event.getType() == Event.EventType.None) {
            connectLatch.countDown();
            return;
        }
        //2. 设置节点删除的监视器
        if (Event.EventType.NodeDeleted == event.getType() && event.getPath().equals(this.lockPath)) {
            System.out.println("[" + id + "]尝试获取锁");
            if (lockStatus == LockStatus.TRY_LOCK && createLockNode()) {
                lockStatus = LockStatus.LOCKED;
                try {
                    lockBarrier.await();
                    System.out.println("[" + id + "]" + " 获取锁");
                    return;
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Boolean createLockNode() {
        try {
            zooKeeper.create(lockPath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (KeeperException | InterruptedException e) {
            return false;
        }

        return true;
    }

    enum LockStatus {
        TRY_LOCK,
        LOCKED,
        UNLOCK
    }
}
